﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace SS2TC.Speech
{
    /// <summary>
    /// Writes audio binary data to play-back device.
    /// </summary>
    public class AudioStreamWriter : AudioStream
    {
        private IntPtr waveOutPointer = new IntPtr();

        private uint waveOutResult;

        /// <summary>
        /// Initializes a new instance of <see cref="AudioStreamWriter"/>.
        /// </summary>
        /// <param name="waveFormat">Format of the output wave.</param>
        public AudioStreamWriter(WaveFormat waveFormat) : base(waveFormat)
        {
        }

        /// <summary>
        /// Opens the output stream.
        /// </summary>
        protected override void Open()
        {
            waveOutResult = NativeMethods.waveOutOpen(
                ref waveOutPointer,
                NativeMethods.WAVE_MAPPER,
                ref nativeWaveFormat,
                IntPtr.Zero,
                IntPtr.Zero,
                (uint)NativeMethods.WaveInOpenFlags.WAVE_FORMAT_DIRECT);

            if (waveOutResult != (uint)NativeMethods.MMRESULT.MMSYSERR_NOERROR)
            {
                throw new InvalidOperationException("Error opening output device");
            }
        }

        /// <summary>
        /// Closes the output stream.
        /// </summary>
        public override void Close()
        {
            while (NativeMethods.waveOutClose(waveOutPointer) != (uint)NativeMethods.MMRESULT.MMSYSERR_NOERROR)
            { }
        }

        /// <summary>
        /// Writes data to the play-back device.
        /// </summary>
        /// <param name="data">Binary data to write.</param>
        public void Write(byte[] data)
        {
            Open();

            waveData = data;
            IntPtr dataPointer = GetIntPtr(waveData);

            waveHeader.dwBufferLength = (uint) waveData.Length;
            waveHeader.lpData = dataPointer;
            waveHeader.dwFlags = 0;
            waveHeader.dwLoops = 0;
            waveHeader.dwBytesRecorded = 0;
            waveHeader.dwUser = IntPtr.Zero;
            waveHeader.lpNext = IntPtr.Zero;
            waveHeader.reserved = IntPtr.Zero;

            // Header size in bytes
            uint waveHeaderSize = Convert.ToUInt32(Marshal.SizeOf(waveHeader));

            waveOutResult = NativeMethods.waveOutPrepareHeader(waveOutPointer, ref waveHeader, (int)waveHeaderSize);

            if (waveOutResult != (uint)NativeMethods.MMRESULT.MMSYSERR_NOERROR)
            {
                throw new InvalidOperationException("Error preparing the wave-out");
            }

            waveOutResult = NativeMethods.waveOutWrite(waveOutPointer, ref waveHeader, waveHeaderSize);

            if (waveOutResult != (uint)NativeMethods.MMRESULT.MMSYSERR_NOERROR)
            {
                throw new InvalidOperationException("Error writing wave-out");
            }
        }
    }
}
